﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.Utilities;

namespace Workflows.Components.Entities.Relations.InferredAssociations
{
    /// <summary>
    /// 
    /// </summary>
    [Serializable]
    public class Cluster
    {
        private int _ClusterID;
        /// <summary></summary>
        public int ClusterID
        {
            get
            {
                return this._ClusterID;
            }
            set
            {
                this._ClusterID = value;
            }
        }

        private EntityMetaData _MetaData;
        /// <summary></summary>
        public EntityMetaData MetaData
        {
            get
            {
                return this._MetaData;
            }
            set
            {
                this._MetaData = value;
            }
        }

        private Dictionary<Entity,double> _EntityMembership;
        /// <summary></summary>
        public Dictionary<Entity,double> EntityMembership
        {
            get
            {
                return this._EntityMembership;
            }
            set
            {
                this._EntityMembership = value;
            }
        }

        private object[] _Center;
        /// <summary></summary>
        public object[] Center
        {
            get
            {
                return this._Center;
            }
            set
            {
                this._Center = value;
            }
        }

        private double _AvgDiameter;
        /// <summary></summary>
        public double AvgDiameter
        {
            get
            {
                return this._AvgDiameter;
            }
            set
            {
                this._AvgDiameter = value;
            }
        }

        public Cluster(int clusterId, EntityMetaData metaData, Dictionary<Entity,double> clusteredEntities)
        {
            this._ClusterID = clusterId;
            this._MetaData = metaData;
            this._EntityMembership=new Dictionary<Entity, double>();
            List<Entity> entities=new List<Entity>();
            foreach(Entity entity in clusteredEntities.Keys)
            {
                this._EntityMembership.Add(entity, clusteredEntities[entity]);
                entities.Add(entity);
            }

            this._Center = this.CalculateClusterCenter(entities);
            this._AvgDiameter = this.CalculateClusterDiameter(entities);
        }

        private object[] CalculateClusterCenter(List<Entity> entities)
        {
            List<object> centerPoints =new List<object>();
            
            foreach (AttributeMetaData attrMetaData in this._MetaData.Attributes)
            {
                object[] attrVals1 = new object[entities.Count];
                double[] associations1 = new double[entities.Count];
                for (int i = 0; i < entities.Count;i++)
                {
                    double membership1 = 1.0;
                    object attrValue1 = entities[i].ComparableAttributes[attrMetaData.AttributeName];
                    attrVals1[i] = attrValue1;
                    associations1[i] = membership1;
                }
                object avgAttrValue = attrMetaData.GetAttributeWeightedValue(attrVals1, associations1);
                MatchResult matchResult = MatchResult.NullMatch;
                object attrDistance = attrMetaData.GetComparer().Compare(avgAttrValue, null, ref matchResult);
                centerPoints.Add(attrDistance);
            }

            return centerPoints.ToArray();
        }

        private double CalculateClusterDiameter(List<Entity> entities)
        {
            List<double> pairWiseDistances=new List<double>();
            foreach(Entity entity1 in entities)
            {
                foreach(Entity entity2 in entities)
                {
                    pairWiseDistances.Add(this._MetaData.CompareEntity(entity1,entity2));
                }
            }
            return StatsUtil.GetAverage(pairWiseDistances).Value;
        }

        public double CalculateDistanceToCluster(Cluster cluster2)
        {
            double sumSq = 0;
            foreach(AttributeMetaData attrMetaData in this._MetaData.Attributes)
            {
                object[] objs1 = new object[this.EntityMembership.Count];
                double[] weights1 = new double[this.EntityMembership.Count];
                Entity[] entities1 = new Entity[this.EntityMembership.Count];
                this.EntityMembership.Keys.CopyTo(entities1, 0);
                for(int i=0;i<entities1.Length;i++)
                {
                    double membership1 = this.EntityMembership[entities1[i]];
                    object attrValue1 = entities1[i].ComparableAttributes[attrMetaData.AttributeName];
                    objs1[i] = attrValue1;
                    weights1[i] = membership1;
                }
                object weightedAvg1 = attrMetaData.GetAttributeWeightedValue(objs1, weights1);
                object[] objs2 = new object[cluster2.EntityMembership.Count];
                double[] weights2 = new double[cluster2.EntityMembership.Count];
                Entity[] entities2 = new Entity[cluster2.EntityMembership.Count];
                cluster2.EntityMembership.Keys.CopyTo(entities2, 0);
                for(int i=0;i<entities2.Length;i++)
                {
                    double membership2 = cluster2.EntityMembership[entities2[i]];
                    object attrValue2 = entities2[i].ComparableAttributes[attrMetaData.AttributeName];
                    objs2[i] = attrValue2;
                    weights2[i] = membership2;
                }
                object weightedAvg2 = attrMetaData.GetAttributeWeightedValue(objs2, weights2);
                MatchResult matchResult = MatchResult.NullMatch;
                double attrDistance = attrMetaData.GetComparer().Compare(weightedAvg1, weightedAvg2, ref matchResult);
                sumSq += attrDistance*attrDistance;
            }
            return Math.Sqrt(sumSq);
        }

        public double CalculateDistanceToEntity(Entity entity)
        {
            double sumSq = 0;
            foreach (AttributeMetaData attrMetaData in this._MetaData.Attributes)
            {
                object[] objs1 = new object[this.EntityMembership.Count];
                double[] weights1 = new double[this.EntityMembership.Count];
                Entity[] entities1 = new Entity[this.EntityMembership.Count];
                this.EntityMembership.Keys.CopyTo(entities1, 0);
                for(int i=0;i<entities1.Length;i++)
                {
                    double membership1 = this.EntityMembership[entities1[i]];
                    object attrValue1 = entities1[i].ComparableAttributes[attrMetaData.AttributeName];
                    objs1[i] = attrValue1;
                    weights1[i] = membership1;
                }
                object weightedAvg1 = attrMetaData.GetAttributeWeightedValue(objs1, weights1);
                object attr2 = entity.ComparableAttributes[attrMetaData.AttributeName];
                MatchResult matchResult = MatchResult.NullMatch;
                double attrDistance = attrMetaData.GetComparer().Compare(weightedAvg1, attr2,ref matchResult);
                sumSq += attrDistance * attrDistance;
            }
            return Math.Sqrt(sumSq);
        }
    }
}
